package unimelb.filesync.common.protocol;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;

import javax.crypto.SecretKey;

import unimelb.filesync.common.message.FileSyncRequest;
import unimelb.filesync.common.misc.BoundedPriorityBlockingQueue;
import unimelb.filesync.common.misc.ProtocolContext;
import unimelb.filesync.common.network.AesEncryptedNetworkConnection;
import unimelb.filesync.common.network.NetworkConnection;
import unimelb.filesync.source.FileChangeMonitorTask;
import filesync.SynchronisedFile;

public abstract class PrepareFileSync implements IFileSyncPhase {

    protected final ProtocolContext context;

    public PrepareFileSync(ProtocolContext context) {
        super();
        this.context = context;
    }

    @Override
    public void execute() {
        System.out.println("Start synchronization");
        
        final String role = context.getObject(ProtocolContext.ROLE);
        try {
            if (ProtocolContext.ROLE_SOURCE.equalsIgnoreCase(role)) {
                setUpAsSource();
            } else if (ProtocolContext.ROLE_DESTINATION.equalsIgnoreCase(role)) {
                setUpAsDestination();
            } else {
                throw new RuntimeException("Unknown role");
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    protected abstract NetworkConnection createConnection();

    private void setUpAsDestination() throws IOException {
        final SynchronisedFile syncFile = setUpSyncFile();

        setUpAdditionalReadConnections();

        setUpInstructionProcessor(syncFile);
    }

    private void setUpAsSource() throws IOException {
        final SynchronisedFile syncFile = setUpSyncFile();

        setUpPopulateInstructionQueueThread(syncFile);

        setUpAddionalWriteConnections();

        setUpNewBlockRequestListener();

        setUpFileWatchMonitor(syncFile);

        Thread.currentThread().setName("NewBlockListener");
    }

    private SynchronisedFile setUpSyncFile() throws IOException {
        final String fileName = context.getObject(ProtocolContext.FILE_NAME);
        final Long blockSize = context.getObject(ProtocolContext.BLOCK_SIZE);

        return new SynchronisedFile(fileName, blockSize.intValue());
    }

    private void setUpNewBlockRequestListener() {
        new Thread(new HandleNewBlockRequestTask(context)).start();
    }

    private void setUpFileWatchMonitor(final SynchronisedFile syncFile) {
        final Integer pollPeriod = context.getObject(ProtocolContext.FILE_CHANGE_POLL);
        final Timer fileWatch = new Timer("FileChangeMonitor");
        fileWatch.scheduleAtFixedRate(new FileChangeMonitorTask(syncFile), 0, pollPeriod);
    }

    private void setUpPopulateInstructionQueueThread(SynchronisedFile syncFile) {
        final Integer queueSize = context.getObject(ProtocolContext.DEFAULT_QUEUE_SIZE);
        context.addObject(ProtocolContext.WRITE_MSG_QUEUE, new BoundedPriorityBlockingQueue<FileSyncRequest>(queueSize));
        
        new Thread(new PopulateInstructionQueueTask(syncFile, context), "InstructionQueueMonitor").start();
    }

    private void setUpAddionalWriteConnections() throws IOException {
        final Integer queueSize = context.getObject(ProtocolContext.DEFAULT_QUEUE_SIZE);
        context.addObject(ProtocolContext.SENT_MSG_QUEUE, new BoundedPriorityBlockingQueue<FileSyncRequest>(queueSize));
        
        final SecretKey key = context.getObject(ProtocolContext.SECRET_KEY);

        final Integer connectionCount = context.getObject(ProtocolContext.MAX_INSTRUCTION_SOCKETS);
        final List<NetworkConnection> connections = new ArrayList<NetworkConnection>();
        for (int i = 0; i < connectionCount; i++) {
            final NetworkConnection unsecuredConnection = createConnection();
            connections.add(new AesEncryptedNetworkConnection(unsecuredConnection, key));
        }

        for (NetworkConnection connection : connections) {
            final Thread t = new Thread(new WriteInstructionTask(connection, context), "InstructionSender");
            t.start();
        }
    }

    private void setUpAdditionalReadConnections() throws IOException {
        final Integer queueSize = context.getObject(ProtocolContext.DEFAULT_QUEUE_SIZE);
        context.addObject(ProtocolContext.READ_MSG_QUEUE, new BoundedPriorityBlockingQueue<FileSyncRequest>(queueSize));
        
        final SecretKey key = context.getObject(ProtocolContext.SECRET_KEY);

        final Integer connectionCount = context.getObject(ProtocolContext.MAX_INSTRUCTION_SOCKETS);
        final List<NetworkConnection> connections = new ArrayList<NetworkConnection>();
        for (int i = 0; i < connectionCount; i++) {
            final NetworkConnection unsecuredConnection = createConnection();
            connections.add(new AesEncryptedNetworkConnection(unsecuredConnection, key));
        }

        for (NetworkConnection connection : connections) {
            final Thread t = new Thread(new ReadInstructionTask(connection, context), "InstructionReader");
            t.start();
        }
    }

    private void setUpInstructionProcessor(SynchronisedFile syncFile) {
        new Thread(new ProcessInstructionTask(syncFile, context), "InstructionProcessor").start();
    }

}
